Skip to content

feat(query-core): add structuralSharing option to useQueries#10101

Open
dagstuan wants to merge 11 commits intoTanStack:mainfrom
dagstuan:main
Open

feat(query-core): add structuralSharing option to useQueries#10101
dagstuan wants to merge 11 commits intoTanStack:mainfrom
dagstuan:main

Conversation

@dagstuan
Copy link

@dagstuan dagstuan commented Feb 6, 2026

🎯 Changes

Add a structuralSharing option to useQueries/createQueries/injectQueries that allows disabling structural sharing for the combined result. When set to false, the combined result will not use replaceEqualDeep for referential stability. Defaults to true.

Full disclosure: Docs were generated by Claude, all other implementation was done without AI.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Added an optional structuralSharing option to useQueries across React, Vue, Svelte, Solid, Preact, and Angular — allows disabling or customizing structural sharing for combined query results (defaults to true).
    • Enabled structuralSharing support in core query logic to honor the new option.
  • Documentation

    • Updated useQueries reference to document structuralSharing and its effect on combined results.
  • Chores

    • Bumped minor versions for multiple query packages and added a changeset entry.

@changeset-bot
Copy link

changeset-bot bot commented Feb 6, 2026

🦋 Changeset detected

Latest commit: e7a7214

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 22 packages
Name Type
@tanstack/angular-query-experimental Minor
@tanstack/svelte-query Minor
@tanstack/react-query Minor
@tanstack/preact-query Minor
@tanstack/solid-query Minor
@tanstack/query-core Minor
@tanstack/vue-query Minor
@tanstack/angular-query-persist-client Major
@tanstack/svelte-query-devtools Major
@tanstack/svelte-query-persist-client Major
@tanstack/react-query-devtools Major
@tanstack/react-query-next-experimental Major
@tanstack/react-query-persist-client Major
@tanstack/preact-query-devtools Major
@tanstack/preact-query-persist-client Major
@tanstack/solid-query-devtools Major
@tanstack/solid-query-persist-client Major
@tanstack/query-async-storage-persister Patch
@tanstack/query-broadcast-client-experimental Patch
@tanstack/query-persist-client-core Patch
@tanstack/query-sync-storage-persister Patch
@tanstack/vue-query-devtools Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new public option structuralSharing?: boolean | ((oldData, newData) => unknown) across query-core and framework adapters to allow disabling or customizing structural sharing for combined useQueries results; threads the flag through optimistic-result computations, observers, docs, tests, and a changeset.

Changes

Cohort / File(s) Summary
Release Configuration
/.changeset/silver-coins-mix.md
Changeset updated to include minor bumps for multiple packages and the structuralSharing feature flag.
Documentation
docs/framework/react/reference/useQueries.md
Documented new `structuralSharing?: boolean
Core Query Logic
packages/query-core/src/queriesObserver.ts
Added structuralSharing to QueriesObserverOptions, extended getOptimisticResult signature, and applied custom/disabled structural sharing in combine/result computation and notify paths.
Core Tests
packages/query-core/src/__tests__/queriesObserver.test.tsx
Updated getOptimisticResult call sites to include the new third argument (structuralSharing/undefined) across tests.
React Implementation
packages/react-query/src/useQueries.ts
Exposed structuralSharing on useQueries options and forwarded it to observer.getOptimisticResult.
Vue Implementation
packages/vue-query/src/useQueries.ts
Added structuralSharing to UseQueriesOptions and forwarded it into observer optimistic-result and refetch paths.
Solid Implementation
packages/solid-query/src/useQueries.ts
Added structuralSharing to queries options and threaded it into observer optimistic-result and update flows.
Svelte Implementation
packages/svelte-query/src/createQueries.svelte.ts
Added structuralSharing to public options, unified derived options handling, and passed it into QueriesObserver and optimistic result creation.
Angular Implementation
packages/angular-query-experimental/src/inject-queries.ts
Added structuralSharing to InjectQueriesOptions and forwarded it to getOptimisticResult during optimistic computation.
Preact Implementation
packages/preact-query/src/useQueries.ts
Added structuralSharing option and forwarded it to observer.getOptimisticResult.

Sequence Diagram(s)

sequenceDiagram
  participant Hook as Framework Hook\n(useQueries)
  participant Observer as QueriesObserver\n(query-core)
  participant Combine as CombineFn\n(user-provided)
  participant Result as Combined Result

  Hook->>Observer: construct(queries, combine?, structuralSharing?)
  Hook->>Observer: getOptimisticResult(queries, combine, structuralSharing)
  Observer->>Observer: compute per-query optimistic results
  alt combine provided
    Observer->>Combine: call combine(per-query results)
    Combine-->>Observer: combined value
    Observer->>Observer: apply structuralSharing?
    Observer-->>Result: combined (possibly shared) result
  else no combine
    Observer-->>Result: array of per-query results
  end
  Observer-->>Hook: optimistic result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • TkDodo

Poem

🐰
I nibble flags and hop through code so spry,
A toggle for sharing—decide how results tie.
Through hooks and core I bound and play,
Share or keep, the choice hops your way! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: adding a structuralSharing option to useQueries in query-core, which is the core feature across all the affected packages.
Description check ✅ Passed The description follows the template structure with all required sections completed: Changes section explains the feature clearly, both checklist items are marked as completed, and release impact indicates a changeset was generated.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/solid-query/src/useQueries.ts (1)

312-332: ⚠️ Potential issue | 🔴 Critical

Bug: structuralSharing is not passed to observer.setQueries() in the Solid adapter.

Both setQueries call sites (lines 313–320 and 324–331) manually construct the options object with only combine, omitting structuralSharing. This causes the observer's internal #options.structuralSharing to be undefined after mount/update, resulting in subscription-driven notifications ignoring the user's structuralSharing: false setting.

All other framework adapters (React, Vue, Svelte, Angular) pass the full options object including structuralSharing to setQueries.

Proposed fix
   onMount(() => {
     observer.setQueries(
       defaultedQueries(),
-      queriesOptions().combine
-        ? ({
-            combine: queriesOptions().combine,
-          } as QueriesObserverOptions<TCombinedResult>)
-        : undefined,
+      queriesOptions() as QueriesObserverOptions<TCombinedResult>,
     )
   })
 
   createComputed(() => {
     observer.setQueries(
       defaultedQueries(),
-      queriesOptions().combine
-        ? ({
-            combine: queriesOptions().combine,
-          } as QueriesObserverOptions<TCombinedResult>)
-        : undefined,
+      queriesOptions() as QueriesObserverOptions<TCombinedResult>,
     )
   })

The extra queries property in queriesOptions() will be passed to setQueries but safely ignored at runtime since QueriesObserverOptions only has combine and structuralSharing properties.

🤖 Fix all issues with AI agents
In @.changeset/silver-coins-mix.md:
- Line 10: The changeset message contains a typo "strucuralSharing"; update the
changes text to read "structuralSharing" so the feature line reads:
feat(query-core): Allow disabling structuralSharing for useQueries, ensuring the
corrected spelling replaces the incorrect "strucuralSharing" token in the
changeset entry.

In `@packages/query-core/src/queriesObserver.ts`:
- Around line 33-37: The JSDoc claim that structuralSharing disables structural
sharing is inaccurate because replaceEqualDeep is only called inside the combine
branch (see combine usage around replaceEqualDeep and return of input), so
structuralSharing: false is a no-op when combine is undefined; either update the
JSDoc (and corresponding adapter docs) to state structuralSharing only applies
when combine is provided, or change the non-combine path to respect the flag by
applying the same replaceEqualDeep logic to the returned input (use the existing
replaceEqualDeep helper and the structuralSharing boolean where the function
currently returns input directly) so structuralSharing:false actually prevents
structural sharing in both code paths (adjust code around the combine check and
the final return to call replaceEqualDeep when appropriate).
🧹 Nitpick comments (1)
packages/query-core/src/__tests__/queriesObserver.test.tsx (1)

296-305: Missing test coverage for structuralSharing: false.

All existing tests pass undefined (defaulting to true). There are no tests verifying that structuralSharing: false actually skips replaceEqualDeep and produces a new reference for the combined result. Consider adding a test that asserts referential inequality when structuralSharing is false with a combine function, and referential equality when it's true.

@dagstuan dagstuan force-pushed the main branch 4 times, most recently from 368a0b7 to 21e9f59 Compare February 6, 2026 20:59
Add a `structuralSharing` option to useQueries/createQueries/injectQueries
that allows disabling structural sharing for the combined result. When set
to `false`, the combined result will not use `replaceEqualDeep` for
referential stability. Defaults to `true`.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docs/framework/react/reference/useQueries.md`:
- Around line 27-31: Update the `structuralSharing` option docs to state the
prerequisite that it "Only applies when `combine` is provided"; specifically, in
the `structuralSharing?: boolean | ((oldData: unknown | undefined, newData:
unknown) => unknown)` description add a concise note that this setting has
effect only if `combine` is supplied, referencing the `combine` option so
readers understand the dependency between `structuralSharing` and `combine`.
🧹 Nitpick comments (1)
packages/svelte-query/src/createQueries.svelte.ts (1)

215-216: Including structuralSharing in derivedCreateQueriesOptions may trigger unnecessary observer recreation.

Since observer is $derived from derivedCreateQueriesOptions (line 229-235), changing only structuralSharing will recreate the entire QueriesObserver. The constructor doesn't use structuralSharing — it's only consumed later by getOptimisticResult and #combineResult. This is the same pre-existing trade-off as combine changes causing recreation, so it's not a regression, but worth noting for potential future optimization.

Also applies to: 229-235

@dagstuan
Copy link
Author

@TkDodo Is there anything left for this to be reviewable again?

@nx-cloud
Copy link

nx-cloud bot commented Mar 2, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit eabd1fc

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ❌ Failed 4m 24s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-02 07:58:59 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 2, 2026

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@10101

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@10101

@tanstack/preact-query

npm i https://pkg.pr.new/@tanstack/preact-query@10101

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@10101

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@10101

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@10101

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@10101

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@10101

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@10101

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@10101

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@10101

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@10101

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@10101

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@10101

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@10101

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@10101

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@10101

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@10101

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@10101

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@10101

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@10101

commit: 687ccc5

Comment on lines 266 to 276
if (typeof structuralSharing === 'function') {
this.#combinedResult = structuralSharing(
this.#combinedResult,
combined,
) as TCombinedResult
} else {
this.#combinedResult =
structuralSharing !== false
? replaceEqualDeep(this.#combinedResult, combined)
: combined
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a replaceData util for this that we should use here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to fix this now, but I'm not sure if the result is satisfactory. The replaceData util required QueryOptions<any, any, any, any> which didn't map cleanly to my usage inside of queriesObserver.. You can take a look and see how you like it, I had to change the signature slightly.

/**
* Set this to `false` to disable structural sharing between query results.
* Set this to a function which accepts the old and new data and returns resolved data of the same type to implement custom structural sharing logic.
* Only applies when `combine` is provided.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m wondering if it should also apply to normal useQueries results without combine?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would that work? Currently all of the logic related to structural sharing and combining of the results in useQueries rely on a combine function to be specified. If the consumer wants custom structural sharing without a comine they can just use the regular structuralSharing option on each query sent into useQueries instead?

@dagstuan
Copy link
Author

dagstuan commented Mar 2, 2026

I attempted to fix your comments now @TkDodo 🙏 Hope this can get merged eventually 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants